/*
 * Copyright (c) 2022 PATEO CONNECT+ (Nanjing) Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


#include "tts_manager.h"
#include "common_utils.h"
#include "voice_assistant_log.h"
#include "voice_cloud_loader.h"
#include <curl/curl.h>
#include <media_errors.h>
#include <nlohmann/json.hpp>
#include <thread>

namespace OHOS {
namespace CarVoiceAssistant {

    class AudioPlayerCallback : public OHOS::Media::PlayerCallback {
    public:
        void OnError(OHOS::Media::PlayerErrorType errorType, int32_t errorCode) override;
        void OnInfo(OHOS::Media::PlayerOnInfoType type, int32_t extra, const OHOS::Media::Format& infoBody) override;

        void PrintState(OHOS::Media::PlayerStates state);

        wptr<TTSManager> manager_;
    };

    void AudioPlayerCallback::OnError(OHOS::Media::PlayerErrorType errorType, int32_t errorCode)
    {
        VOICE_ASSISTANT_LOGI("AudioPlayerCallback::OnError:%{public}d", errorCode);
    }

    void AudioPlayerCallback::OnInfo(OHOS::Media::PlayerOnInfoType type, int32_t extra, const OHOS::Media::Format& infoBody)
    {
        if (type == OHOS::Media::INFO_TYPE_STATE_CHANGE) {
            OHOS::Media::PlayerStates state = static_cast<OHOS::Media::PlayerStates>(extra);
            PrintState(state);
            if (state == OHOS::Media::PlayerStates::PLAYER_STARTED) {
                if (manager_ != nullptr) {
                    manager_->OnPlayStateChanged(true);
                }
            } else if (state == OHOS::Media::PlayerStates::PLAYER_PAUSED
                || state == OHOS::Media::PlayerStates::PLAYER_STOPPED
                || state == OHOS::Media::PlayerStates::PLAYER_PLAYBACK_COMPLETE
                || state == OHOS::Media::PlayerStates::PLAYER_IDLE
                || state == OHOS::Media::PlayerStates::PLAYER_STATE_ERROR) {
                if (manager_ != nullptr) {
                    manager_->OnPlayStateChanged(false);
                }
            }
        }
    }

    void AudioPlayerCallback::PrintState(OHOS::Media::PlayerStates state)
    {
        static const std::map<OHOS::Media::PlayerStates, std::string> STATE_MAP = {
            { OHOS::Media::PlayerStates::PLAYER_STATE_ERROR, "Error" },
            { OHOS::Media::PlayerStates::PLAYER_IDLE, "Idle" },
            { OHOS::Media::PlayerStates::PLAYER_INITIALIZED, "Initialized" },
            { OHOS::Media::PlayerStates::PLAYER_PREPARED, "Prepared" },
            { OHOS::Media::PlayerStates::PLAYER_STARTED, "Started" },
            { OHOS::Media::PlayerStates::PLAYER_PAUSED, "Paused" },
            { OHOS::Media::PlayerStates::PLAYER_STOPPED, "Stopped" },
            { OHOS::Media::PlayerStates::PLAYER_PLAYBACK_COMPLETE, "Complete" },
        };

        VOICE_ASSISTANT_LOGI("AudioPlayerCallback::PrintState:%{public}s", STATE_MAP.at(state).c_str());
    }

    MediaDataSource::MediaDataSource(void* data, size_t size)
        : data_(data)
        , size_(size)
    {
    }

    MediaDataSource::~MediaDataSource()
    {
        if (data_) {
            free(data_);
        }
        data_ = nullptr;
    }

    int32_t MediaDataSource::ReadAt(int64_t pos, uint32_t length, const std::shared_ptr<OHOS::Media::AVSharedMemory>& mem)
    {
        VOICE_ASSISTANT_LOGI("ReadAt pos %{public}lld length %{public}u", pos, length);

        if (mem->GetSize() <= 0) {
            VOICE_ASSISTANT_LOGE("mem size should large than 0");
            return OHOS::Media::SOURCE_ERROR_IO;
        }

        if (pos >= size_) {
            VOICE_ASSISTANT_LOGE("is eof");
            return OHOS::Media::SOURCE_ERROR_EOF;
        }

        length = std::min(static_cast<uint32_t>(size_ - pos), length);

        uint32_t realLen = std::min(length, static_cast<uint32_t>(mem->GetSize()));

        if (mem->GetBase() == nullptr) {
            VOICE_ASSISTANT_LOGE("mem->GetBase() is nullptr");
            return OHOS::Media::SOURCE_ERROR_IO;
        }

        memcpy(mem->GetBase(), (char*)data_ + pos, static_cast<size_t>(realLen));

        VOICE_ASSISTANT_LOGI("length %{public}u realLen %{public}u", length, realLen);
        return realLen;
    }

    int32_t MediaDataSource::ReadAt(uint32_t length, const std::shared_ptr<OHOS::Media::AVSharedMemory>& mem)
    {
        (void)length;
        (void)mem;
        return 0;
    }

    int32_t MediaDataSource::GetSize(int64_t& size)
    {
        size = size_;
        return OHOS::Media::MSERR_OK;
    }

    TTSManager::TTSManager()
        : voiceCloudManager_(nullptr)
        , currentText_("")
        , speakerType_("common")
        , player_(nullptr)
        , callback_(nullptr)
        , isAudioPlaying_(false)
    {
        curl_global_init(CURL_GLOBAL_ALL);
    }

    TTSManager::~TTSManager()
    {
        curl_global_cleanup();
        if (player_ != nullptr) {
            if (player_->IsPlaying()) {
                player_->Stop();
            }
            player_->Release();
            player_ = nullptr;
        }
        voiceCloudManager_ = nullptr;
    }

    void TTSManager::RequestPlay(std::string text)
    {
        mutex_.lock();
        currentText_ = text;
        mutex_.unlock();

        std::thread requestThread(&TTSManager::RunRequest, this, text);
        requestThread.detach();
    }

    void TTSManager::RunRequest(std::string text)
    {
        if (voiceCloudManager_ == nullptr) {
            VOICE_ASSISTANT_LOGI("voiceCloudManager_ is nullptr");
            return;
        }

        MemoryStruct memoryStruct = voiceCloudManager_->RequestTTS(text, speakerType_);

        if (memoryStruct.size == 0) {
            VOICE_ASSISTANT_LOGI("RunRequest:TTS data is zero");
            return;
        }

        mutex_.lock();
        bool isNeedPlay = currentText_.compare(text) == 0;
        mutex_.unlock();

        if (isNeedPlay) {
            PlayTTS(memoryStruct.memory, memoryStruct.size);
        }
    }

    void TTSManager::CancelAll()
    {
        std::lock_guard<std::mutex> lock(mutex_);
        currentText_ = "";
        CancelPlayTTS();
    }

    void TTSManager::ChangeSpeakerType(std::string speakerType)
    {
        speakerType_ = speakerType;
    }

    void TTSManager::PlayTTS(void* data, size_t length)
    {
        CancelPlayTTS();
        VOICE_ASSISTANT_LOGI("TTS Play tts length:%{public}d", length);

        player_recursive_mutex_.lock();
        if (player_ == nullptr) {
            player_ = OHOS::Media::PlayerFactory::CreatePlayer();
            std::shared_ptr<AudioPlayerCallback> cb = std::make_shared<AudioPlayerCallback>();
            cb->manager_ = this;
            player_->SetPlayerCallback(cb);
        }
        source_ = std::make_shared<MediaDataSource>(data, length);
        player_->SetSource(source_);
        player_->Prepare();
        player_->Play();
        player_recursive_mutex_.unlock();
        OnPlayStateChanged(true);
    }

    void TTSManager::CancelPlayTTS()
    {
        VOICE_ASSISTANT_LOGI("TTS Cancel Play ");
        player_recursive_mutex_.lock();
        if (player_ != nullptr) {
            if (player_->IsPlaying()) {
                player_->Stop();
                player_->Reset();
            }
            player_->Release();
            player_ = nullptr;
        }
        player_recursive_mutex_.unlock();

        OnPlayStateChanged(false);
    }

    bool TTSManager::GetAudioPlayerIsPlaying()
    {
        return isAudioPlaying_;
    }

    void TTSManager::OnPlayStateChanged(bool isPlaying)
    {
        if (isAudioPlaying_ == isPlaying) {
            return;
        }
        isAudioPlaying_ = isPlaying;
        if (callback_ != nullptr) {
            callback_->AudioPlayerStatusChanged(true);
        }
    }

    void TTSManager::SetCallback(wptr<ITTSManagerCallback> callback)
    {
        callback_ = callback;
    }

}
}
